package keyintegrity.orm.jpa;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;

/**
 *
 * @author dmitrygusev
 */
public class Storage {
	
    private static Map<String, Storage> instances = new HashMap<String, Storage>();
    
	private String persistenceUnit;
	
    public static Storage getInstance(String persistenceUnit) {
    	Storage instance = instances.get(persistenceUnit);
    	
        if (instance == null) {
            instance = new Storage(persistenceUnit);
            instances.put(persistenceUnit, instance);
        }
        
        return instance;
    }
    
    private ExceptionDecorator decorator;
    
    public void setDecorator(ExceptionDecorator decorator) {
		this.decorator = decorator;
	}
    
    public ExceptionDecorator getDecorator() {
		return decorator;
	}

    EntityManagerFactory emf;

	private Map<Class<?>, Persister<?>> persisters;
	private Map<Class<?>, Updater<?>> updaters;
    
    private Storage(String persistenceUnit) {
        this.persistenceUnit = persistenceUnit;
        this.persisters = new HashMap<Class<?>, Persister<?>>();
        this.updaters = new HashMap<Class<?>, Updater<?>>();
        emf = createEMF();
    }

	EntityManagerFactory createEMF() {
		return Persistence.createEntityManagerFactory(persistenceUnit);
	}

    EntityManager getEM() {
	    return emf.createEntityManager();
	}

    public <T> void registerPersister(Class<T> clazz, Persister<T> persister) {
    	this.persisters.put(clazz, persister);
    }
    
    public <T> void registerUpdater(Class<T> clazz, Updater<T> updater) {
    	this.updaters.put(clazz, updater);
    }
    
	public boolean ping() {
	    try {
	        return getEM() != null;
	    } catch (Exception e) {
	        e.printStackTrace();
	        
	        return false;
	    }
	}
	
	public <T> List<T> queryList(final String query, final int maxResults) {
	    ReliableQuery<T> rq = new ReliableQuery<T>(this) {
	    	@SuppressWarnings("unchecked")
			@Override
	    	public List<T> query(EntityManager em) {
	    		Query q = em.createQuery(query);
		        q.setMaxResults(maxResults);
		        return q.getResultList();
	    	}
	    };
	    
	    return rq.execute();
	}

	public <T> List<T> unreliableQueryList(
			EntityManager em, 
			String query, 
			int maxResults, 
			String paramName, Object paramValue) {
		
        return unreliableQueryList(em, query, maxResults, 
        		new Parameter(paramName, paramValue));
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> unreliableQueryList(
			EntityManager em, 
			String query, 
			int maxResults, 
			Parameter... parameters) {
		
		Query q = em.createQuery(query);
        q.setMaxResults(maxResults);
        
        if (parameters != null && parameters.length > 0) {
        	for (Parameter parameter : parameters) {
				q.setParameter(parameter.getName(), parameter.getValue());
			}
        }
        
        return q.getResultList();
		
	}
	
	public <T> List<T> queryList(final String query, final int maxResults, 
			final String paramName, final Object paramValue) {

		return queryList(query, maxResults, new Parameter(paramName, paramValue));

	}

	public <T> List<T> queryList(
			final String query, 
			final int maxResults, 
			final Parameter... parameters) {
		
		ReliableQuery<T> rq = new ReliableQuery<T>(this) {
	    	@SuppressWarnings("unchecked")
			@Override
	    	public List<T> query(EntityManager em) {
	    		Query q = em.createQuery(query);
		        q.setMaxResults(maxResults);

		        if (parameters != null && parameters.length > 0) {
		        	for (Parameter parameter : parameters) {
						q.setParameter(parameter.getName(), parameter.getValue());
					}
		        }

		        return q.getResultList();
	    	}
	    };
	    
	    return rq.execute();
	}

	/**
	 * Сохранить объект, по возможности используя соответствующий {@link Persister}.
	 * Если для класса <code>T</code> не зарегистрирован {@link Persister}, используется
	 * метод {@link #rawPersist(Object)}.
	 * 
	 * @param <T>
	 * @param object
	 * @return
	 * @throws StorageException
	 */
	@SuppressWarnings("unchecked")
	public <T> T persist(final T object) throws StorageException {
		Class<?> clazz = object.getClass();
		
		Persister<?> persister = persisters.get(clazz);
		
		if (persister != null && persister.compatibleWith(object)) {
			return ((Persister<T>) persister).persist(this, object);
		}
		
		return rawPersist(object);
	}
	
	/**
	 * Сохранить объект, используя {@link EntityManager#persist(Object)} и {@link ReliableQuery}.
	 * 
	 * @param <T>
	 * @param object
	 * @return
	 * @throws StorageError
	 * @throws StorageException
	 */
	public <T> T rawPersist(final T object) throws StorageError,
			StorageException {
		ReliableQuery<T> rq = new ReliableQuery<T>(this) {
			public List<T> query(EntityManager em) {
				em.persist(object);
				return null;
			}
		};
		
		try {
			rq.execute();
		} catch (Exception e) {
			throw new StorageException(this, e);
		}
		
	    return object;
	}

	/**
	 * Сохранить объект, по возможности используя соответствующий {@link Persister}.
	 * Если для класса <code>T</code> не зарегистрирован {@link Persister}, используется
	 * метод {@link #rawPersist(Object)}.
	 * 
	 * @param <T>
	 * @param object
	 * @return
	 * @throws StorageException
	 */
	@SuppressWarnings("unchecked")
	public <T> T update(final T object) throws StorageException {
		Class<?> clazz = object.getClass();
		
		Updater<?> updater = updaters.get(clazz);
		
		if (updater != null && updater.compatibleWith(object)) {
			return ((Updater<T>) updater).update(this, object);
		}
		
		return rawUpdate(object);
	}
	
	public <T> T rawUpdate(final T object) throws StorageException {
		ReliableQuery<T> rq = new ReliableQuery<T>(this) {
			public List<T> query(EntityManager em) {
				em.merge(object);
				return null;
			}
		};
		
		try {
			rq.execute();
		} catch (Exception e) {
			throw new StorageException(this, e);
		}
		
	    return object;
	}
	
	public int executeUpdate(String query, String paramName, Object paramValue) throws StorageException {
		
		return executeUpdate(query, new Parameter(paramName, paramValue));
		
	}

	public int executeUpdate(String query, Parameter... parameters) throws StorageException {
		try {
			return reliableUpdate(query, parameters);
		} catch (Exception e) {
			throw new StorageException(this, e);
		}
	}

	private int reliableUpdate(final String query, final Parameter... parameters) {
		ReliableQuery<Integer> rq = new ReliableQuery<Integer>(this) {
			public List<Integer> query(EntityManager em) {
				List<Integer> result = new ArrayList<Integer>();
		            
	            Query q = em.createQuery(query);
	            
	            if (parameters != null && parameters.length > 0) {
	            	for (Parameter parameter : parameters) {
	    	            q.setParameter(parameter.getName(), parameter.getValue());
					}
	            }
	            
				result.add(q.executeUpdate());
		        
				return result;
			}
		};
		
		return rq.execute().get(0);
	}

	public <T> T querySingle(String query, Parameter... parameters) {
		List<T> result = queryList(query, 1, parameters);
		
		return result.isEmpty() 
		     ? null
		     : result.get(0); 
	}

	public <T> T querySingle(String query, String paramName, Object paramValue) {

		List<T> result = queryList(query, 1, new Parameter(paramName, paramValue));
		
		return result.isEmpty() 
		     ? null
		     : result.get(0); 

	}

	
} 